package geecache

//提供被其他节点访问的能力(基于http)

import (
	"fmt"
	"geecache/consistenthash"
	"io"
	"log"
	"net/http"
	"net/url"
	"strings"
	"sync"
	pb "geecache/geecachepb"
	"github.com/golang/protobuf/proto"
)

const (
	defaultBasePath = "/_geecache/"
	defaultReplicas = 50
)
//HTTPPool implements PeerPicker for a pook of http peers
type HTTPPool struct{
	self string//用来记录自己的地址，包括主机名/IP 和端口
	basePath string//作为节点间通讯地址的前缀
	mu  sync.Mutex // guards peers and httpGetters
	peers *consistenthash.Map //类型是一致性哈希算法的 Map，
	//用来根据具体的 key 选择节点
	httpGetters map[string]*httpGetter
	//映射远程节点与对应的 httpGetter
}

//NewHTTPPool initializes an HTTP pool of peers
func NewHTTPPool(self string) *HTTPPool{
	return &HTTPPool{
		self:self,
		basePath: defaultBasePath,
	}
}

// Log info with server name
func (p *HTTPPool) Log(format string, v ...interface{}) {
	log.Printf("[Server %s] %s", p.self, fmt.Sprintf(format, v...))
}

// ServeHTTP handle all http requests
func (p *HTTPPool)ServeHTTP(w http.ResponseWriter,r *http.Request){
	if !strings.HasPrefix(r.URL.Path,p.basePath){
		panic("HTTPPool serving unexpected path:"+r.URL.Path)
	}
	p.Log("%s %s",r.Method,r.URL.Path)
	// /<basepath>/<groupname>/<key> required
	parts := strings.SplitN(r.URL.Path[len(p.basePath):],"/",2)
	if len(parts) != 2{
		http.Error(w,"bad request",http.StatusBadRequest)
		return
	}
	groupName := parts[0]
	key := parts[1]
	group := GetGroup(groupName)
	if group == nil{
		http.Error(w,"no such group:"+groupName,http.StatusNotFound)
		return
	}
	view,err := group.Get(key)
	body,err := proto.Marshal(&pb.Response{Value:view.ByteSlice()})
	if err != nil{
		http.Error(w,err.Error(),http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type","application/octet-stream")
	w.Write(body)
}

//httpGetter 具体的 HTTP 客户端类
type httpGetter struct{
	baseURL string
}

func (h *httpGetter)Get(in *pb.Request,out *pb.Response)error{
	u := fmt.Sprintf(
		"%v%v/%v",
		h.baseURL,
		url.QueryEscape(in.GetGroup()),
		url.QueryEscape(in.GetKey()),
	)
	res,err := http.Get(u)
	if err != nil{
		return err
	}
	defer res.Body.Close()
	if res.StatusCode != http.StatusOK{
		return fmt.Errorf("server return: %v",res.Status)
	}
	bytes,err := io.ReadAll(res.Body)
	if err != nil{
		return fmt.Errorf("reading response body:%v",err)
	}
	if err =proto.Unmarshal(bytes,out);err != nil{
		return fmt.Errorf("decoding response body:%v",err)
	}
	return nil
}

//Set updates the pool's list of peers
//Set() 方法实例化了一致性哈希算法，并且添加了传入的节点。
//并为每一个节点创建了一个 HTTP 客户端 httpGetter。
func (p *HTTPPool)Set(peers ...string){
	p.mu.Lock()
	defer p.mu.Unlock()
	p.peers = consistenthash.New(defaultReplicas,nil)
	p.peers.Add(peers...)
	p.httpGetters = make(map[string]*httpGetter,len(peers))
	for _,peer := range peers{
		p.httpGetters[peer] = &httpGetter{baseURL: peer+p.basePath}
		log.Println("httpGetter:",peer+p.basePath)
	}
}

//PickPeer picks a peer according to key
//包装了一致性哈希算法的 Get() 方法，
//根据具体的 key，选择节点，返回节点对应的 HTTP 客户端。
func (p *HTTPPool)PickPeer(key string) (PeerGetter,bool){
	p.mu.Lock()
	defer p.mu.Unlock()
	if peer := p.peers.Get(key);peer != ""&&peer != p.self{
		p.Log("Pick peer %s",peer)
		return p.httpGetters[peer],true
	}
	return nil,false
}
//接口检查
var _ PeerPicker = (*HTTPPool)(nil)
var _ PeerGetter = (*httpGetter)(nil)